Source for file class.inputfilter.php

Documentation is available at class.inputfilter.php

  1. <?php
  2.  
  3. /** @class: InputFilter (PHP4 & PHP5, with comments)
  4.   * @project: PHP Input Filter
  5.   * @date: 10-05-2005
  6.   * @version: 1.2.2_php4/php5
  7.   * @author: Daniel Morris
  8.   * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
  9.   * @copyright: Daniel Morris
  10.   * @email: dan@rootcube.com
  11.   * @license: GNU General Public License (GPL)
  12.   */
  13. class InputFilter {
  14.     var $tagsArray;            // default = empty array
  15.     var $attrArray;            // default = empty array
  16.  
  17.     var $tagsMethod;        // default = 0
  18.     var $attrMethod;        // default = 0
  19.  
  20.     var $xssAuto;           // default = 1
  21.     var $tagBlacklist = array('applet''body''bgsound''base''basefont''embed''frame''frameset''head''html''id''iframe''ilayer''layer''link''meta''name''object''script''style''title''xml');
  22.     var $attrBlacklist = array('action''background''codebase''dynsrc''lowsrc');  // also will strip ALL event handlers
  23.  
  24.     /**
  25.       * Constructor for inputFilter class. Only first parameter is required.
  26.       * @access constructor
  27.       * @param Array $tagsArray - list of user-defined tags
  28.       * @param Array $attrArray - list of user-defined attributes
  29.       * @param int $tagsMethod - 0= allow just user-defined, 1= allow all but user-defined
  30.       * @param int $attrMethod - 0= allow just user-defined, 1= allow all but user-defined
  31.       * @param int $xssAuto - 0= only auto clean essentials, 1= allow clean blacklisted tags/attr
  32.       */
  33.     function inputFilter($tagsArray array()$attrArray array()$tagsMethod 0$attrMethod 0$xssAuto 1{
  34.         // make sure user defined arrays are in lowercase
  35.         for ($i 0$i count($tagsArray)$i++$tagsArray[$istrtolower($tagsArray[$i]);
  36.         for ($i 0$i count($attrArray)$i++$attrArray[$istrtolower($attrArray[$i]);
  37.         // assign to member vars
  38.         $this->tagsArray = (array) $tagsArray;
  39.         $this->attrArray = (array) $attrArray;
  40.         $this->tagsMethod = $tagsMethod;
  41.         $this->attrMethod = $attrMethod;
  42.         $this->xssAuto = $xssAuto;
  43.     }
  44.  
  45.     /**
  46.       * Method to be called by another php script. Processes for XSS and specified bad code.
  47.       * @access public
  48.       * @param Mixed $source - input string/array-of-string to be 'cleaned'
  49.       * @return String $source - 'cleaned' version of input parameter
  50.       */
  51.     function process($source{
  52.         // clean all elements in this array
  53.         if (is_array($source)) {
  54.             foreach($source as $key => $value)
  55.                 // filter element for XSS and other 'bad' code etc.
  56.                 if (is_string($value)) $source[$key$this->remove($this->decode($value));
  57.             return $source;
  58.         // clean this string
  59.         else if (is_string($source)) {
  60.             // filter source for XSS and other 'bad' code etc.
  61.             return $this->remove($this->decode($source));
  62.         // return parameter as given
  63.         else return $source;
  64.     }
  65.  
  66.     /**
  67.       * Internal method to iteratively remove all unwanted tags and attributes
  68.       * @access protected
  69.       * @param String $source - input string to be 'cleaned'
  70.       * @return String $source - 'cleaned' version of input parameter
  71.       */
  72.     function remove($source{
  73.         $loopCounter=0;
  74.         // provides nested-tag protection
  75.         while($source != $this->filterTags($source)) {
  76.             $source $this->filterTags($source);
  77.             $loopCounter++;
  78.         }
  79.         return $source;
  80.     }
  81.  
  82.     /**
  83.       * Internal method to strip a string of certain tags
  84.       * @access protected
  85.       * @param String $source - input string to be 'cleaned'
  86.       * @return String $source - 'cleaned' version of input parameter
  87.       */
  88.     function filterTags($source{
  89.         // filter pass setup
  90.         $preTag NULL;
  91.         $postTag $source;
  92.         // find initial tag's position
  93.         $tagOpen_start strpos($source'<');
  94.         // interate through string until no tags left
  95.         while($tagOpen_start !== FALSE{
  96.             // process tag interatively
  97.             $preTag .= substr($postTag0$tagOpen_start);
  98.             $postTag substr($postTag$tagOpen_start);
  99.             $fromTagOpen substr($postTag1);
  100.             // end of tag
  101.             $tagOpen_end strpos($fromTagOpen'>');
  102.             if ($tagOpen_end === falsebreak;
  103.             // next start of tag (for nested tag assessment)
  104.             $tagOpen_nested strpos($fromTagOpen'<');
  105.             if (($tagOpen_nested !== false&& ($tagOpen_nested $tagOpen_end)) {
  106.                 $preTag .= substr($postTag0($tagOpen_nested+1));
  107.                 $postTag substr($postTag($tagOpen_nested+1));
  108.                 $tagOpen_start strpos($postTag'<');
  109.                 continue;
  110.             }
  111.             $tagOpen_nested (strpos($fromTagOpen'<'$tagOpen_start 1);
  112.             $currentTag substr($fromTagOpen0$tagOpen_end);
  113.             $tagLength strlen($currentTag);
  114.             if (!$tagOpen_end{
  115.                 $preTag .= $postTag;
  116.                 $tagOpen_start strpos($postTag'<');
  117.             }
  118.             // iterate through tag finding attribute pairs - setup
  119.             $tagLeft $currentTag;
  120.             $attrSet array();
  121.             $currentSpace strpos($tagLeft' ');
  122.             // is end tag
  123.             if (substr($currentTag01== "/"{
  124.                 $isCloseTag TRUE;
  125.                 list($tagNameexplode(' '$currentTag);
  126.                 $tagName substr($tagName1);
  127.             // is start tag
  128.             else {
  129.                 $isCloseTag FALSE;
  130.                 list($tagNameexplode(' '$currentTag);
  131.             }
  132.             // excludes all "non-regular" tagnames OR no tagname OR remove if xssauto is on and tag is blacklisted
  133.             if ((!preg_match("/^[a-z][a-z0-9]*$/i",$tagName)) || (!$tagName|| ((in_array(strtolower($tagName)$this->tagBlacklist)) && ($this->xssAuto))) {
  134.                 $postTag substr($postTag($tagLength 2));
  135.                 $tagOpen_start strpos($postTag'<');
  136.                 // don't append this tag
  137.                 continue;
  138.             }
  139.             // this while is needed to support attribute values with spaces in!
  140.             while ($currentSpace !== FALSE{
  141.                 $fromSpace substr($tagLeft($currentSpace+1));
  142.                 $nextSpace strpos($fromSpace' ');
  143.                 $openQuotes strpos($fromSpace'"');
  144.                 $closeQuotes strpos(substr($fromSpace($openQuotes+1))'"'$openQuotes 1;
  145.                 // another equals exists
  146.                 if (strpos($fromSpace'='!== FALSE{
  147.                     // opening and closing quotes exists
  148.                     if (($openQuotes !== FALSE&& (strpos(substr($fromSpace($openQuotes+1))'"'!== FALSE))
  149.                         $attr substr($fromSpace0($closeQuotes+1));
  150.                     // one or neither exist
  151.                     else $attr substr($fromSpace0$nextSpace);
  152.                 // no more equals exist
  153.                 else $attr substr($fromSpace0$nextSpace);
  154.                 // last attr pair
  155.                 if (!$attr$attr $fromSpace;
  156.                 // add to attribute pairs array
  157.                 $attrSet[$attr;
  158.                 // next inc
  159.                 $tagLeft substr($fromSpacestrlen($attr));
  160.                 $currentSpace strpos($tagLeft' ');
  161.             }
  162.             // appears in array specified by user
  163.             $tagFound in_array(strtolower($tagName)$this->tagsArray);
  164.             // remove this tag on condition
  165.             if ((!$tagFound && $this->tagsMethod|| ($tagFound && !$this->tagsMethod)) {
  166.                 // reconstruct tag with allowed attributes
  167.                 if (!$isCloseTag{
  168.                     $attrSet $this->filterAttr($attrSet);
  169.                     $preTag .= '<' $tagName;
  170.                     for ($i 0$i count($attrSet)$i++)
  171.                         $preTag .= ' ' $attrSet[$i];
  172.                     // reformat single tags to XHTML
  173.                     if (strpos($fromTagOpen"</" $tagName)) $preTag .= '>';
  174.                     else $preTag .= ' />';
  175.                 // just the tagname
  176.                 else $preTag .= '</' $tagName '>';
  177.             }
  178.             // find next tag's start
  179.             $postTag substr($postTag($tagLength 2));
  180.             $tagOpen_start strpos($postTag'<');
  181.         }
  182.         // append any code after end of tags
  183.         $preTag .= $postTag;
  184.         return $preTag;
  185.     }
  186.  
  187.     /**
  188.       * Internal method to strip a tag of certain attributes
  189.       * @access protected
  190.       * @param Array $attrSet 
  191.       * @return Array $newSet
  192.       */
  193.     function filterAttr($attrSet{
  194.         $newSet array();
  195.         // process attributes
  196.         for ($i 0$i <count($attrSet)$i++{
  197.             // skip blank spaces in tag
  198.             if (!$attrSet[$i]continue;
  199.             // split into attr name and value
  200.             $attrSubSet explode('='trim($attrSet[$i]),2);
  201.             list($attrSubSet[0]explode(' '$attrSubSet[0]);
  202.             // removes all "non-regular" attr names AND also attr blacklisted
  203.             if ((!eregi("^[a-z]*$",$attrSubSet[0])) || (($this->xssAuto&& ((in_array(strtolower($attrSubSet[0])$this->attrBlacklist)) || (substr($attrSubSet[0]02== 'on'))))
  204.                 continue;
  205.             // xss attr value filtering
  206.             if ($attrSubSet[1]{
  207.                 // strips unicode, hex, etc
  208.                 $attrSubSet[1str_replace('&#'''$attrSubSet[1]);
  209.                 // strip normal newline within attr value
  210.                 $attrSubSet[1preg_replace('/\s+/'''$attrSubSet[1]);
  211.                 // strip double quotes
  212.                 $attrSubSet[1str_replace('"'''$attrSubSet[1]);
  213.                 // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
  214.                 if ((substr($attrSubSet[1]01== "'"&& (substr($attrSubSet[1](strlen($attrSubSet[1]1)1== "'"))
  215.                     $attrSubSet[1substr($attrSubSet[1]1(strlen($attrSubSet[1]2));
  216.                 // strip slashes
  217.                 $attrSubSet[1stripslashes($attrSubSet[1]);
  218.             }
  219.             // auto strip attr's with "javascript:
  220.             if (InputFilter::badAttributeValue$attrSubSet ))
  221.                 continue;
  222.  
  223.             // if matches user defined array
  224.             $attrFound in_array(strtolower($attrSubSet[0])$this->attrArray);
  225.             // keep this attr on condition
  226.             if ((!$attrFound && $this->attrMethod|| ($attrFound && !$this->attrMethod)) {
  227.                 // attr has value
  228.                 if ($attrSubSet[1]$newSet[$attrSubSet[0'="' $attrSubSet[1'"';
  229.                 // attr has decimal zero as value
  230.                 else if ($attrSubSet[1== "0"$newSet[$attrSubSet[0'="0"';
  231.                 // reformat single attributes to XHTML
  232.                 else $newSet[$attrSubSet[0'="' $attrSubSet[0'"';
  233.             }
  234.         }
  235.         return $newSet;
  236.     }
  237.  
  238.     /**
  239.      * Function to determine if contents of an attribute is safe
  240.      * @param Array A 2 element array for attribute [name] and [value]
  241.      * @return Boolean True if bad code is detected
  242.      */
  243.     function badAttributeValue$attrSubSet {
  244.         $attrSubSet[0strtolower$attrSubSet[0);
  245.         $attrSubSet[1strtolower$attrSubSet[1);
  246.         return (
  247.             ((strpos($attrSubSet[1]'expression'!== false&& ($attrSubSet[0]== 'style'||
  248.             (strpos($attrSubSet[1]'javascript:'!== false||
  249.             (strpos($attrSubSet[1]'behaviour:'!== false||
  250.             (strpos($attrSubSet[1]'vbscript:'!== false||
  251.             (strpos($attrSubSet[1]'mocha:'!== false||
  252.             (strpos($attrSubSet[1]'livescript:'!== false)
  253.         );
  254.     }
  255.  
  256.     /**
  257.       * Try to convert to plaintext
  258.       * @access protected
  259.       * @param String $source 
  260.       * @return String $source
  261.       */
  262.     function decode($source{
  263.         // url decode
  264.         $source html_entity_decode($sourceENT_QUOTES"ISO-8859-1");
  265.         // convert decimal
  266.         $source preg_replace('/&#(\d+);/me',"chr(\\1)"$source);                // decimal notation
  267.         // convert hex
  268.         $source preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\\1)"$source);    // hex notation
  269.         return $source;
  270.     }
  271.  
  272.     /**
  273.       * Method to be called by another php script. Processes for SQL injection
  274.       * @access public
  275.       * @param Mixed $source - input string/array-of-string to be 'cleaned'
  276.       * @param Buffer $connection - An open MySQL connection
  277.       * @return String $source - 'cleaned' version of input parameter
  278.       */
  279.     function safeSQL($source&$connection{
  280.         // clean all elements in this array
  281.         if (is_array($source)) {
  282.             foreach($source as $key => $value)
  283.                 // filter element for SQL injection
  284.                 if (is_string($value)) $source[$key$this->quoteSmart($this->decode($value)$connection);
  285.             return $source;
  286.         // clean this string
  287.         else if (is_string($source)) {
  288.             // filter source for SQL injection
  289.             if (is_string($source)) return $this->quoteSmart($this->decode($source)$connection);
  290.         // return parameter as given
  291.         else return $source;
  292.     }
  293.  
  294.     /**
  295.       * @author Chris Tobin
  296.       * @author Daniel Morris
  297.       * @access protected
  298.       * @param String $source 
  299.       * @param Resource $connection - An open MySQL connection
  300.       * @return String $source
  301.       */
  302.     function quoteSmart($source&$connection{
  303.         // strip slashes
  304.         if (get_magic_quotes_gpc()) $source stripslashes($source);
  305.         // quote both numeric and text
  306.         $source $this->escapeString($source$connection);
  307.         return $source;
  308.     }
  309.  
  310.     /**
  311.       * @author Chris Tobin
  312.       * @author Daniel Morris
  313.       * @access protected
  314.       * @param String $source 
  315.       * @param Resource $connection - An open MySQL connection
  316.       * @return String $source
  317.       */
  318.     function escapeString($string&$connection{
  319.         // depreciated function
  320.         if (version_compare(phpversion(),"4.3.0""<")) mysql_escape_string($string);
  321.         // current function
  322.         else mysql_real_escape_string($string);
  323.         return $string;
  324.     }
  325. }
  326.  
  327. ?>

Documentation generated on Mon, 05 May 2008 16:17:10 +0400 by phpDocumentor 1.4.0